/*-------------------------------------------------------------------------
 *
 * nodeSamplescan.c
 *	  支持关系的样本扫描的例程（表采样）。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/executor/nodeSamplescan.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/relscan.h"
#include "access/tableam.h"
#include "access/tsmapi.h"
#include "common/pg_prng.h"
#include "executor/executor.h"
#include "executor/nodeSamplescan.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "storage/bufmgr.h"
#include "storage/predicate.h"
#include "utils/builtins.h"
#include "utils/rel.h"

static TupleTableSlot *fc_SampleNext(SampleScanState *fc_node);
static void fc_tablesample_init(SampleScanState *fc_scanstate);
static TupleTableSlot *fc_tablesample_getnext(SampleScanState *fc_scanstate);

/* ----------------------------------------------------------------
 *						扫描支持
 * ----------------------------------------------------------------
 */

/* ----------------------------------------------------------------
 *		SampleNext
 *
 *		这是 ExecSampleScan 的主力军
 * ----------------------------------------------------------------
 */
static TupleTableSlot * fc_SampleNext(SampleScanState *fc_node)
{
	/*
	 * 如果这是扫描中的第一次调用，请初始化
	 */
	if (!fc_node->begun)
		fc_tablesample_init(fc_node);

	/*
	 * 获取下一个元组，并将其存储在我们的结果插槽中
	 */
	return fc_tablesample_getnext(fc_node);
}

/*
 * SampleRecheck -- 访问方法例程以在 EvalPlanQual 中重新检查元组
 */
static bool fc_SampleRecheck(SampleScanState *fc_node, TupleTableSlot *fc_slot)
{
	/*
	 * 对于 SampleScan 不需要重新检查，因为与 SeqScan 一样我们不传递任何
	 * 可检查的键到 heap_beginscan。
	 */
	return true;
}

/* ----------------------------------------------------------------
 *		ExecSampleScan(node)
 *
 *		使用采样方法扫描关系并返回
 *		下一个满足条件的元组。
 *		我们调用 ExecScan() 例程并传递适当的
 *		访问方法函数。
 * ----------------------------------------------------------------
 */
static TupleTableSlot * fc_ExecSampleScan(PlanState *fc_pstate)
{
	SampleScanState *fc_node = castNode(SampleScanState, fc_pstate);

	return ExecScan(&fc_node->ss,
					(ExecScanAccessMtd) fc_SampleNext,
					(ExecScanRecheckMtd) fc_SampleRecheck);
}

/* ----------------------------------------------------------------
 *		ExecInitSampleScan
 * ----------------------------------------------------------------
 */
SampleScanState *
ExecInitSampleScan(SampleScan *fc_node, EState *fc_estate, int fc_eflags)
{
	SampleScanState *fc_scanstate;
	TableSampleClause *fc_tsc = fc_node->tablesample;
	TsmRoutine *fc_tsm;

	Assert(outerPlan(fc_node) == NULL);
	Assert(innerPlan(fc_node) == NULL);

	/*
	 * 创建状态结构
	 */
	fc_scanstate = makeNode(SampleScanState);
	fc_scanstate->ss.ps.plan = (Plan *) fc_node;
	fc_scanstate->ss.ps.state = fc_estate;
	fc_scanstate->ss.ps.ExecProcNode = fc_ExecSampleScan;

	/*
	 * 杂项初始化
	 *
	 * 为节点创建表达式上下文
	 */
	ExecAssignExprContext(fc_estate, &fc_scanstate->ss.ps);

	/*
	 * 打开扫描关系
	 */
	fc_scanstate->ss.ss_currentRelation =
		ExecOpenScanRelation(fc_estate,
							 fc_node->scan.scanrelid,
							 fc_eflags);

	/* 我们稍后不会设置 HeapScanDesc */
	fc_scanstate->ss.ss_currentScanDesc = NULL;

	/* 并创建具有适当行类型的槽 */
	ExecInitScanTupleSlot(fc_estate, &fc_scanstate->ss,
						  RelationGetDescr(fc_scanstate->ss.ss_currentRelation),
						  table_slot_callbacks(fc_scanstate->ss.ss_currentRelation));

	/*
	 * 初始化结果类型和投影。
	 */
	ExecInitResultTypeTL(&fc_scanstate->ss.ps);
	ExecAssignScanProjectionInfo(&fc_scanstate->ss);

	/*
	 * 初始化子表达式
	 */
	fc_scanstate->ss.ps.qual =
		ExecInitQual(fc_node->scan.plan.qual, (PlanState *) fc_scanstate);

	fc_scanstate->args = ExecInitExprList(fc_tsc->args, (PlanState *) fc_scanstate);
	fc_scanstate->repeatable =
		ExecInitExpr(fc_tsc->repeatable, (PlanState *) fc_scanstate);

	/*
	 * 如果我们没有 REPEATABLE 子句，则选择随机种子。 
	 * 我们希望仅执行一次，因为种子在重新扫描时不应更改。
	 */
	if (fc_tsc->repeatable == NULL)
		fc_scanstate->seed = pg_prng_uint32(&pg_global_prng_state);

	/*
	 * 最后，初始化 TABLESAMPLE 方法处理程序。
	 */
	fc_tsm = GetTsmRoutine(fc_tsc->tsmhandler);
	fc_scanstate->tsmroutine = fc_tsm;
	fc_scanstate->tsm_state = NULL;

	if (fc_tsm->InitSampleScan)
		fc_tsm->InitSampleScan(fc_scanstate, fc_eflags);

	/* 我们稍后会执行 BeginSampleScan；我们还不能评估参数 */
	fc_scanstate->begun = false;

	return fc_scanstate;
}

/* ----------------------------------------------------------------
 *		ExecEndSampleScan
 *
 *		释放通过 C 例程分配的任何存储。
 * ----------------------------------------------------------------
 */
void ExecEndSampleScan(SampleScanState *fc_node)
{
	/*
	 * 告诉采样函数我们完成了扫描。
	 */
	if (fc_node->tsmroutine->EndSampleScan)
		fc_node->tsmroutine->EndSampleScan(fc_node);

	/*
	 * 释放表达式上下文
	 */
	ExecFreeExprContext(&fc_node->ss.ps);

	/*
	 * 清空元组表
	 */
	if (fc_node->ss.ps.ps_ResultTupleSlot)
		ExecClearTuple(fc_node->ss.ps.ps_ResultTupleSlot);
	ExecClearTuple(fc_node->ss.ss_ScanTupleSlot);

	/*
	 * 关闭堆扫描
	 */
	if (fc_node->ss.ss_currentScanDesc)
		table_endscan(fc_node->ss.ss_currentScanDesc);
}

/* ----------------------------------------------------------------
 *		ExecReScanSampleScan
 *
 *		重新扫描关系。
 *
 * ----------------------------------------------------------------
 */
void ExecReScanSampleScan(SampleScanState *fc_node)
{
	/* 记住我们需要再次执行 BeginSampleScan（如果我们曾经执行过的话） */
	fc_node->begun = false;
	fc_node->done = false;
	fc_node->haveblock = false;
	fc_node->donetuples = 0;

	ExecScanReScan(&fc_node->ss);
}


/*
 * 初始化 TABLESAMPLE 方法：评估参数并调用 BeginSampleScan。
 */
static void fc_tablesample_init(SampleScanState *fc_scanstate)
{
	TsmRoutine *fc_tsm = fc_scanstate->tsmroutine;
	ExprContext *fc_econtext = fc_scanstate->ss.ps.ps_ExprContext;
	Datum	   *fc_params;
	Datum		fc_datum;
	bool		fc_isnull;
	uint32		fc_seed;
	bool		fc_allow_sync;
	int			fc_i;
	ListCell   *fc_arg;

	fc_scanstate->donetuples = 0;
	fc_params = (Datum *) palloc(list_length(fc_scanstate->args) * sizeof(Datum));

	fc_i = 0;
	foreach(fc_arg, fc_scanstate->args)
	{
		ExprState  *fc_argstate = (ExprState *) lfirst(fc_arg);

		fc_params[fc_i] = ExecEvalExprSwitchContext(fc_argstate,
											  fc_econtext,
											  &fc_isnull);
		if (fc_isnull)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLESAMPLE_ARGUMENT),
					 errmsg("TABLESAMPLE parameter cannot be null")));
		fc_i++;
	}

	if (fc_scanstate->repeatable)
	{
		fc_datum = ExecEvalExprSwitchContext(fc_scanstate->repeatable,
										  fc_econtext,
										  &fc_isnull);
		if (fc_isnull)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TABLESAMPLE_REPEAT),
					 errmsg("TABLESAMPLE REPEATABLE parameter cannot be null")));

		/*
		 * REPEATABLE 参数已通过解析器转换为 float8。
		 * 在 SQL 层面使用 float8 的原因是它将
		 * 为习惯于只在 REPEATABLE 子句中接受整数的用户和
		 * 可能期望 REPEATABLE 像 setseed() 工作（一个范围在 -1 到 1 的浮点数）的用户
		 * 产生令人惊讶的结果。
		 *
		 * 我们使用 hashfloat8() 将提供的值转换为合适的
		 * 种子。为了回归测试的目的，这具有方便的
		 * 特性，即 REPEATABLE(0) 产生一个机器无关的结果。
		 */
		fc_seed = DatumGetUInt32(DirectFunctionCall1(hashfloat8, fc_datum));
	}
	else
	{
		/* 使用 ExecInitSampleScan 选定的种子 */
		fc_seed = fc_scanstate->seed;
	}

	/* 为 BeginSampleScan 可以调整的参数设置默认值 */
	fc_scanstate->use_bulkread = true;
	fc_scanstate->use_pagemode = true;

	/* 让 tablesample 方法发挥作用 */
	fc_tsm->BeginSampleScan(fc_scanstate,
						 fc_params,
						 list_length(fc_scanstate->args),
						 fc_seed);

	/* 如果没有 NextSampleBlock 函数，我们将使用同步扫描 */
	fc_allow_sync = (fc_tsm->NextSampleBlock == NULL);

	/* 现在我们可以创建或重置 HeapScanDesc */
	if (fc_scanstate->ss.ss_currentScanDesc == NULL)
	{
		fc_scanstate->ss.ss_currentScanDesc =
			table_beginscan_sampling(fc_scanstate->ss.ss_currentRelation,
									 fc_scanstate->ss.ps.state->es_snapshot,
									 0, NULL,
									 fc_scanstate->use_bulkread,
									 fc_allow_sync,
									 fc_scanstate->use_pagemode);
	}
	else
	{
		table_rescan_set_params(fc_scanstate->ss.ss_currentScanDesc, NULL,
								fc_scanstate->use_bulkread,
								fc_allow_sync,
								fc_scanstate->use_pagemode);
	}

	pfree(fc_params);

	/* 我们已初始化。 */
	fc_scanstate->begun = true;
}

/*
 * 从 TABLESAMPLE 方法获取下一个元组。
 */
static TupleTableSlot * fc_tablesample_getnext(SampleScanState *fc_scanstate)
{
	TableScanDesc fc_scan = fc_scanstate->ss.ss_currentScanDesc;
	TupleTableSlot *fc_slot = fc_scanstate->ss.ss_ScanTupleSlot;

	ExecClearTuple(fc_slot);

	if (fc_scanstate->done)
		return NULL;

	for (;;)
	{
		if (!fc_scanstate->haveblock)
		{
			if (!table_scan_sample_next_block(fc_scan, fc_scanstate))
			{
				fc_scanstate->haveblock = false;
				fc_scanstate->done = true;

				/* 耗尽关系 */
				return NULL;
			}

			fc_scanstate->haveblock = true;
		}

		if (!table_scan_sample_next_tuple(fc_scan, fc_scanstate, fc_slot))
		{
			/*
			 * 如果我们到这里，意味着我们已耗尽了此页面上的项，现在是时候移动到下一个。
			 */
			fc_scanstate->haveblock = false;
			continue;
		}

		/* 找到可见元组，返回它。 */
		break;
	}

	fc_scanstate->donetuples++;

	return fc_slot;
}
